<template>
  <div
    class="WaterFall-container"
    ref="container"
    :style="`height: ${containerHeight}px`"
  >
    <template v-for="src of srcs" :key="src">
      <img
        class="WaterFall-image"
        :src="src"
        :alt="src"
        :style="`width: ${imageWidth}px`"
      />
    </template>
  </div>
</template>

<script setup lang="ts">
import type { WaterFallProps } from '@/types/components/WaterFall';
import { ref, computed, onMounted, onUnmounted, onUpdated } from 'vue';
import { debounce } from '@/util/util';
const props = withDefaults(defineProps<WaterFallProps>(), {
  minGap: 10,
  singleWidth: 220,
  updateThreshold: 200,
});

const container = ref<HTMLElement | null>(null);
const containerWidth = ref<number>(0);
const containerHeight = ref<number>(0);
const imageWidth = computed<number>(() =>
  Math.min(containerWidth.value, props.singleWidth),
);
const numberOfImagesPerRow = computed(() =>
  Math.max(
    Math.floor(
      (containerWidth.value + props.minGap) / (imageWidth.value + props.minGap),
    ),
    1,
  ),
);
const xGap = computed(() => {
  const rawGap =
    (containerWidth.value - numberOfImagesPerRow.value * imageWidth.value) /
    (numberOfImagesPerRow.value - 1);
  return rawGap === Infinity || isNaN(rawGap) ? props.minGap : rawGap;
});

const images = ref<HTMLElement[]>(
  container.value
    ? (Array.from(container.value.children) as HTMLElement[])
    : [],
);
function setImages() {
  const nextTops = new Array(numberOfImagesPerRow.value);
  nextTops.fill(0);

  images.value.forEach(image => {
    const minTop = Math.min(...nextTops);
    const minTopIndex = nextTops.indexOf(minTop);
    image.style.top = `${minTop}px`;
    image.style.left = `${minTopIndex * (imageWidth.value + xGap.value)}px`;
    let imageClientHeight: number = 0;
    if (image.clientWidth !== imageWidth.value) {
      // hasn't updated clientHeight, calculate by ourselves.
      imageClientHeight =
        (image.clientHeight / image.clientWidth) * imageWidth.value;
    } else {
      imageClientHeight = image.clientHeight;
    }
    nextTops[minTopIndex] += imageClientHeight + xGap.value;
  });
  // update container's height
  containerHeight.value = Math.max(...nextTops) - xGap.value;
}
function handleImageOnload() {
  setImages();
}
const debouncedHandleImageOnload = debounce(
  handleImageOnload,
  props.updateThreshold,
);
function updateImages() {
  if (images.value) {
    images.value.forEach(image => {
      image.removeEventListener('load', debouncedHandleImageOnload);
    });
  }
  if (container.value) {
    images.value = Array.from(container.value.children) as HTMLElement[];
    images.value.forEach(image => {
      image.addEventListener('load', debouncedHandleImageOnload);
    });
  }
}

function handleContainerResize() {
  containerWidth.value = container.value ? container.value.clientWidth : 0;
  setImages();
}
const debouncedHandleContainerResize = debounce(
  handleContainerResize,
  props.updateThreshold,
);
onMounted(() => {
  window.addEventListener('resize', debouncedHandleContainerResize);
  updateImages();
  handleContainerResize();
});
onUpdated(() => {
  updateImages();
  handleContainerResize();
});

onUnmounted(() => {
  window.removeEventListener('resize', debouncedHandleContainerResize);
  if (images.value) {
    images.value.forEach(image => {
      image.removeEventListener('load', handleImageOnload);
    });
  }
});
</script>

<style scoped lang="scss">
.WaterFall-container {
  border: solid 1px #000000;
  position: relative;
}
.WaterFall-image {
  position: absolute;
  transition: all 0.2s ease;
}
</style>
